import io from "socket.io-client";
import * as Y from "yjs";
import {
  Awareness,
  encodeAwarenessUpdate,
  applyAwarenessUpdate,
} from "y-protocols/awareness";
// 一般不用加密 能存到本地的数据 都是有权限 看或者写的
export class CustomSocketProvider {
  private socket: any;
  public awareness: Awareness;
  public canWrite: boolean = true; // 兼容旧服务端：默认可写，若服务端下发权限则以服务端为准
  private wasDisconnected: boolean = false;

  private handleOnline = () => {
    console.log('browser online -> connect');
    try { this.socket.connect(); } catch (e) { console.error('online connect error', e); }
  };
  private handleOffline = () => {
    console.log('browser offline -> disconnect');
    try { this.socket.disconnect(); } catch (e) { console.error('offline disconnect error', e); }
  };

  constructor(serverUrl: string, roomName: string, ydoc: Y.Doc) {
    console.log("正在初始化CustomSocketProvider，服务器地址:", serverUrl);

    // 防止重复发送sync-step1的标志
    let syncStep1Sent = false;

    // 创建一个 socket 连接，配置连接参数
    this.socket = io(serverUrl, {
      // 传输方式：允许 websocket 与 polling，确保 Engine.IO 在需要时能使用带凭证的 polling
      transports: ["websocket", "polling"],

      // 为 polling 传输开启 withCredentials（类型正确的位置）
      transportOptions: {
        polling: { withCredentials: true }
      },

      // 强制创建新连接：每次都创建全新的连接，不复用现有连接（避免缓存问题）
      forceNew: true,

      // 连接超时时间：20秒内无法建立连接则触发 connect_error 事件
      timeout: 20000,

      // 自动连接：创建 Socket 实例时自动尝试连接服务器
      autoConnect: true,

      // 启用自动重连：连接断开时自动尝试重新连接
      reconnection: true,

      // 重连延迟：第一次重连尝试前等待 1 秒
      reconnectionDelay: 1000,
      // 重连最大延迟：退避上限
      reconnectionDelayMax: 5000,

      // 最大重连次数：最多尝试重连 5 次，超过后触发 reconnect_failed 事件
      reconnectionAttempts: 5,

      // === 心跳相关配置（可选，通常使用服务器默认值） ===

      // 心跳超时时间：应该大于等于服务器的 pingTimeout（60秒）
      // 如果在此时间内未收到服务器心跳，客户端认为连接断开
      // timeout: 65000,  // 比服务器稍长一点

      // 是否升级传输方式：从 polling 升级到 websocket（当前只用websocket所以无关）
      upgrade: true,

      // 记住升级状态：避免重复升级检测
      // rememberUpgrade: true、
      rememberUpgrade: false
    });

    // 初始化一个 Awareness 实例
    this.awareness = new Awareness(ydoc);

    // 浏览器网络状态联动
    window.addEventListener('online', this.handleOnline);
    window.addEventListener('offline', this.handleOffline);

    // === 连接状态事件监听 ===

    // 连接成功事件：当与服务器建立连接时触发
    this.socket.on("connect", () => {
      console.log(this.wasDisconnected ? "WebSocket重连成功！" : "WebSocket连接成功！", "Socket ID:", this.socket.id, "recovered=", (this.socket as any).recovered);
      this.wasDisconnected = false;
      // 连接成功后立即加入指定房间，用于文档协同
      console.log(roomName, 'roomName');

      if (roomName) {
        console.log("即将加入房间:", roomName);
        this.socket.emit("joinRoom", { roomName });

        // sync-step1 ：将本地数据状态向量发送给服务端。
        // sync-step2 ：对比数据库数据状态 ，服务端返回缺失的数据，客户端应用这些数据。

        // 防止重复发送的统一发送函数
        const sendStep1 = () => {
          if (syncStep1Sent) {
            console.log("sync-step1 已发送，跳过重复发送");
            return;
          }
          try {
            const vector = Y.encodeStateVector(ydoc);
            const buf = vector.buffer.slice(vector.byteOffset, vector.byteOffset + vector.byteLength);
            console.log("正在发送 sync-step1，字节数:", vector.byteLength);
            //  buf:state vector 状态 向量
            // 发送本地 文档数据 等状态向量 ，socket服务拿到这个向量 和 数据里的数据 进行diff
            // 对比 把缺失的数据 通过sync - step2 发送给客户端
            this.socket.emit("sync-step1", buf);
            syncStep1Sent = true;
            console.log("sync-step1 发送成功");
          } catch (err) {
            console.error("发送 sync-step1 失败", err);
          }
        };

        // 优先通过joined事件触发，500ms兜底（只会执行一次）
        this.socket.once("joined", ({ roomName: joined }: { roomName: string }) => {
          // 因为 如果立即发送 sync-step1 ，服务端可能还没有注册 sync-step1 事件，导致 sync-step1 事件丢失
          if (joined === roomName) {
            console.log("收到joined事件，准备发送 sync-step1");
            sendStep1();
          }
        });

        // 兜底机制：如果500ms后还没收到joined事件，主动发送
        setTimeout(() => {
          if (!syncStep1Sent) {
            console.log("兜底触发 sync-step1");
            sendStep1();
          }
        }, 500);


        // 加入后主动广播本地 awareness（presence），便于服务端/其他客户端感知
        try {
          const localState = this.awareness.getLocalState();
          if (localState) {
            const awarenessFull = encodeAwarenessUpdate(this.awareness, [this.awareness.clientID]);
            const buf = awarenessFull.buffer.slice(awarenessFull.byteOffset, awarenessFull.byteOffset + awarenessFull.byteLength);
            this.socket.emit("awareness-update", buf);
          }
        } catch (err) {
          console.error("发送本地 awareness 失败", err);
        }
      } else {
        console.warn("joinRoom 跳过：roomName 为空");
      }
    });


    // 连接错误事件：连接失败时触发（如服务器未启动、网络问题等）
    this.socket.on("connect_error", (error: any) => {
      console.error("WebSocket连接错误:", error);
      console.error("错误详情:", {
        message: error.message,        // 错误消息
        description: error.description, // 错误描述
        context: error.context,        // 错误上下文
        type: error.type              // 错误类型
      });
    });

    // 一般错误事件：连接建立后发生的其他错误
    this.socket.on("error", (error: any) => {
      console.error("WebSocket运行时错误:", error);
    });

    // 连接断开事件：与服务器失去连接时触发
    this.socket.on("disconnect", (reason: any) => {
      console.log("WebSocket连接断开，原因:", reason);
      this.wasDisconnected = true;
      // 断开连接时重置sync-step1状态，以便重连后能重新发送
      syncStep1Sent = false;

      // 如果是服务器主动断开连接，需要手动重连（因为可能是服务器重启）
      if (reason === "io server disconnect") {
        console.log("服务器主动断开，尝试重新连接...");
        this.socket.connect();
      }
    });

    // === 重连相关事件监听 ===
    // 你用的是 socket.io v4，成功重连通常不会在 Socket 实例上触发 reconnect，而是只触发一次 connect。是否为“重连”可通过 socket.recovered 或“之前是否断开”的状态判断。
    // v4 的重连事件多数在 Manager 层（this.socket.io）上发出；只监听 this.socket 的reconnect/reconnect_attempt 往往看不到。
    // Manager 层重连事件（v4更可靠）
    this.socket.io.on("reconnect_attempt", (attemptNumber: number) => {
      console.log("Manager重连尝试:", attemptNumber);
    });
    this.socket.io.on("reconnect_error", (error: any) => {
      console.error("Manager重连错误:", error);
    });

    // 重连完全失败事件：达到最大重连次数后仍然失败时触发
    this.socket.io.on("reconnect_failed", () => {
      console.log('重连失败');
      console.error("WebSocket重连完全失败！已达到最大重试次数", this.socket.io.opts.reconnectionAttempts);
      console.error("请检查网络连接和服务器状态");
    });

    this.socket.io.on("reconnect", (attemptNumber: number) => {
      console.log("Manager重连成功，重试次数:", attemptNumber);
      // 重连后重置sync-step1状态，允许重新发送
      syncStep1Sent = false;

      // 重连后需要重新加入房间，sync-step1会通过connect事件处理
      if (roomName) {
        console.log("重连后重新加入房间:", roomName);
        // 注意：这里不直接发送sync-step1，而是通过connect事件的逻辑统一处理
        // 因为reconnect事件可能在connect事件之前触发

        // 重连后同步本地 awareness 状态
        try {
          const localState = this.awareness.getLocalState();
          if (localState) {
            const awarenessFull = encodeAwarenessUpdate(this.awareness, [this.awareness.clientID]);
            const buf = awarenessFull.buffer.slice(awarenessFull.byteOffset, awarenessFull.byteOffset + awarenessFull.byteLength);
            this.socket.emit("awareness-update", buf);
          }
        } catch (err) {
          console.error("重连后发送本地 awareness 失败", err);
        }
      }
    });
    this.socket.on("permission-denied", () => {
      console.log('无权修改文档');

    });



    // === 心跳相关事件监听（可选，用于调试） ===

    // 监听服务器发送的心跳包：服务器每25秒发送一次
    this.socket.on("ping", () => {
      console.log("收到服务器心跳包 ping，客户端将自动回复 pong");
    });

    // 监听心跳响应：客户端发送心跳后收到服务器的响应
    this.socket.on("pong", (latency: any) => {
      console.log("收到服务器心跳响应 pong，延迟:", latency, "ms");
    });

    // 监听 sync-doc 事件，该事件会在 websocket 服务器推送过来完整的文档数据的时候会触发
    // 那么要做的事情：同步我当前客户端的 yjs 文档状态
    // 应用场景：文档初次同步
    // 这里意味着回头 websocket 服务器可以触发这么一个事件
    // this.socket.on("sync-doc", (update: ArrayBuffer) => {
    //   console.log("收到 sync-doc，字节数:", (update as ArrayBuffer).byteLength);
    //   try {
    //     Y.applyUpdate(ydoc, new Uint8Array(update));
    //   } catch (err) {
    //     console.error("应用 sync-doc 失败", err);
    //   }
    // });

    // 兼容 y-protocols 差量同步：接收服务器返回的缺失 diff（sync-step2）
    this.socket.on("sync-step2", (diff: ArrayBuffer) => {
      console.log("收到 sync-step2，字节数:", (diff as ArrayBuffer).byteLength);
      try {
        Y.applyUpdate(ydoc, new Uint8Array(diff));
      } catch (err) {
        console.error("应用 sync-step2 diff 失败", err);
      }
    });

    // websocket 服务器推送过来文档的增量更新内容的时候触发
    // 这里意味着回头 websocket 服务器可以触发这么一个事件
    this.socket.on("update", (update: ArrayBuffer) => {
      try {
        Y.applyUpdate(ydoc, new Uint8Array(update));
      } catch (err) {
        console.error("应用 update 失败", err);
      }
    });

    // 接收服务端下发的权限信息
    this.socket.on("permission", ({ permission, canWrite }: { permission: 'read' | 'write' | 'admin'; canWrite: boolean; }) => {
      console.log('permission from server', permission, canWrite);
      this.canWrite = !!canWrite;
    });

    // 只读拦截提示
    this.socket.on("permission-denied", (payload: any) => {
      console.log('写入被拒绝:', payload);
    });

    // 当本地文档有变化的时候，需要将 update 进行一个广播
    ydoc.on("update", (update: Uint8Array) => {
      // 只读状态下，不向服务器发送更新

      if (!this.canWrite) return;
      const buf = update.buffer.slice(update.byteOffset, update.byteOffset + update.byteLength);
      this.socket.emit("update", buf);
      //       这句代码的作用：从 Uint8Array update 的底层 ArrayBuffer 中，按视图的起始偏移 byteOffset 和长度 byteLength，切出“恰好这一段有效字节”的新 ArrayBuffer。
      // 为什么要这样做：update.buffer 可能比视图更大（同一底层缓冲区被多段视图复用）。若直接发送 update.buffer，会把无关字节一并带上；用 slice(byteOffset, byteOffset + byteLength) 可以只发送这次增量对应的那段有效数据，避免偏移导致的脏数据。
      // 关于“是不是只获取本次修改/删除/新增的数据？”
      // ydoc.on('update') 给你的 update 本身就是 Yjs 的“增量更新包”（包含这次编辑产生的插入/删除/格式变化等操作的二进制编码），不是整份文档。
      // 这行代码并不计算增量，它只是从 update 的底层缓冲区里“裁剪出正确的那一段”。换言之，增量已经由 Yjs 生成，这里只是确保传输的字节范围正确。
    });

    // 在进行协同的时候，除了需要同步文档的内容，还需要同步一些额外的信息
    // 谁在线？谁在输入？谁的光标在什么位置？
    // 这些信息虽然是临时的，但是也很重要，需要进行同步
    // 当本地用户的光标信息发生变化，会触发 update 事件
    this.awareness.on(
      "update",
      ({
        added,
        updated,
        removed,
      }: {
        added: number[]; // 新增的用户的 id 列表
        updated: number[]; // 已存在的用户状态发生了变化（例如光标位置变了）
        removed: number[]; // 离开了协作编辑器的用户的 id
      }) => {
        // console.log('awareness update');

        // 需要将变化的状态信息打包成二进制数据，方便网络传输
        const update = encodeAwarenessUpdate(this.awareness, [
          ...added,
          ...updated,
          ...removed,
        ]);
        // 发送给websocket服务器
        // 这里同样意味着回头我们在写 websocket 服务器的时候，需要监听这么一个 awareness-update 的事件
        // 统一传输为 ArrayBuffer（精确切片）以兼容新的 Socket 服务端实现
        const buf = update.buffer.slice(update.byteOffset, update.byteOffset + update.byteLength);
        this.socket.emit("awareness-update", buf);
      }
    );

    // 同理，websocket 服务器给你推送过来的其他用户的新的光标信息
    // 你这边也需要更新
    this.socket.on("awareness-update", (update: ArrayBuffer) => {
      // console.log('socket awareness-update');
      applyAwarenessUpdate(
        this.awareness,
        new Uint8Array(update),
        this.socket.id
      );
    });
  }

  /** 对外暴露的权限订阅，返回取消订阅函数 */
  public onPermission(handler: (payload: { permission: 'read' | 'write' | 'admin'; canWrite: boolean }) => void) {
    this.socket.on('permission', handler as any);
    return () => this.socket.off('permission', handler as any);
  }

  /** 获取当前是否可写 */
  public getCanWrite() {
    return this.canWrite;
  }

  /**
   * 销毁方法，断开websocket连接的时候调用
   */
  destroy() {
    this.socket.disconnect();
  }
}
